package net.towee.server.authentication.impl;

import java.io.IOException;
import java.util.Locale;

import javax.inject.Inject;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.towee.model.account.AccountData;
import net.towee.model.account.AccountIdentity;
import net.towee.model.account.impl.AccountDataImpl;
import net.towee.model.account.impl.AccountIdentityImpl;
import net.towee.model.utils.Pair;
import net.towee.server.authentication.ConfirmEntry;
import net.towee.server.authentication.EmailAuth;
import net.towee.server.authentication.EmailSender;
import net.towee.server.authentication.PasswordDigest;
import net.towee.server.authentication.SessionAuthManager;
import net.towee.server.gxp.EmailConfirmed;
import net.towee.server.persistence.AccountStore;
import net.towee.server.persistence.ConfirmStore;
import net.towee.server.util.RandomBase64Generator;
import net.towee.shared.exceptions.PersistenceException;

import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gxp.base.GxpContext;
import com.mycila.event.internal.guava.base.Preconditions;
import com.mycila.event.internal.guava.base.Strings;

/**
 * Implementation of authentication by email
 * 
 * @author c58
 */
public class EmailAuthServlet extends HttpServlet implements EmailAuth {
	private static final long serialVersionUID = 1885425791279487964L;
	private static final Logger LOG = LoggerFactory.getLogger(EmailAuthServlet.class);

	private final AccountStore accountStore;
	private final SessionAuthManager sessionManager;
	private final ConfirmStore confirmStore;
	private final EmailSender emailSender;

	private final RandomBase64Generator random = new RandomBase64Generator();

	@Inject
	public EmailAuthServlet(AccountStore accountStore, SessionAuthManager sessionManager,
			ConfirmStore confirmStore, EmailSender emailSender){
		
		this.accountStore = accountStore;
		this.sessionManager = sessionManager;
		this.confirmStore = confirmStore;
		this.emailSender = emailSender;
	}

	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response) {
		try {
			processEmailAuthentication(request.getParameter("email"), request.getParameter("password"), 
					request.getSession(false), response.getWriter());
		} catch (IOException e) {
			LOG.error("Error while responding message: ", e);
		}
	}
	
	@Override
	public void doGet(HttpServletRequest request, HttpServletResponse response) {
		try {
			if( processEmailConfirming(request.getParameter("confirm"), request.getSession(false),
					response.getWriter()) )
				response.setHeader("Location", "/");
		} catch (IOException e) {
			LOG.error("Error while responding message: ", e);
		}
	}
	
	@Override
	public void processEmailAuthentication(String email, String password, HttpSession session, Appendable out) {
		try {
			if (!Strings.isNullOrEmpty( email )) {
				if (Strings.isNullOrEmpty( password )) {
					// Try to get acc with this email
					try {
						accountStore.getAccountByEmail(email);
						out.append(EmailAuth.PASSW_VALIDATION_ERROR);
					} catch (PersistenceException e1) {
						// Create confirm entry and password
						ConfirmEntry entry = new ConfirmEntry(null, email);
						password = random.next(8);

						// Try to send email with link
						if (sendConfirmLink(email, password, entry.getConfirm())) {
							// Create new account and save confirm
							entry.setAccountId(createAccount(password));
							confirmStore.addConfirmEntry(entry);

							// Set session as authorized
							sessionManager.setLoggedInUser(session, entry.getAccountId());
						} else {
							LOG.warn("Error while sending email. Maybe not valid email: " + email);
							out.append(EmailAuth.EMAIL_VALIDATION_ERROR);
						}
					}
				} else {
					try {
						String userId = authenticateUser(email, password);
						
						if(userId == null)
							out.append(EmailAuth.PASSW_VALIDATION_ERROR);
						else 
							sessionManager.setLoggedInUser(session, userId);
					} catch (PersistenceException e) {
						LOG.warn("Error while logging in by email. Account not exists: " + email);
						out.append(EmailAuth.ACC_NOT_EXISTS_ERROR);
					}
				}
			}	
			else
				out.append(EmailAuth.EMAIL_VALIDATION_ERROR);
			
		} catch (IOException e) {
			LOG.error("Error while responding message: ", e);
		}
	}
	
	@Override
	public boolean processEmailConfirming(String confirm, HttpSession session, Appendable out) {
		Preconditions.checkArgument( !Strings.isNullOrEmpty(confirm) );
		Preconditions.checkNotNull( session );
		
		try {
			// Get entry with given confirm unique string and try to get account 
			ConfirmEntry entry = createConfirmEmailEntry( confirm );
				
			if(entry != null) {
				accountStore.getAccountById(entry.getAccountId(), "_id");
				
				// Attach email to account
				accountStore.attachEmail( entry.getAccountId(), entry.getEmail() );
				
				// Send response
				if( sessionManager.isLoggedIn(session) ) {
					EmailConfirmed.write(out, new GxpContext(Locale.ENGLISH), entry.getEmail());
					return false;
				}
			}
		} catch (IOException e) {
			LOG.error("Error while responding message: ", e);
		} catch (PersistenceException e) {
			LOG.error("Error getting account while confirming email");
		}
		
		return true;
	}

	@Override
	public ConfirmEntry createConfirmEmailEntry(String confirm) {
		Preconditions.checkArgument( !Strings.isNullOrEmpty(confirm) );
		
		try {
			ConfirmEntry entry = confirmStore.getConfirmEntry( confirm );
			confirmStore.removeConfirmEntry( confirm );
			return entry;
		} catch (PersistenceException e) {
			LOG.error("Error while getting confirm entry: ", e);
		}
		
		return null;
	}

	@Override
	public String authenticateUser(String email, String password) throws PersistenceException {
		Preconditions.checkArgument( !Strings.isNullOrEmpty(email) );
		Preconditions.checkArgument( !Strings.isNullOrEmpty(password) );
		
		AccountData acc = accountStore.getAccountByEmail(email);
		Pair<byte[], byte[]> sd = acc.getSaltDigest();
		PasswordDigest digest = new PasswordDigest(sd.getFirst(), sd.getSecond());
		
		if (!digest.verify(password.toCharArray()))
			return null;
		
		return acc.getId();
	}

	@Override
	public String createAccount(String password) {
		Preconditions.checkArgument( !Strings.isNullOrEmpty(password) );
		
		// Create identity
		AccountIdentity id = new AccountIdentityImpl(new ObjectId().toString());
		
		// Create account
		AccountData acc = new AccountDataImpl( id );
		PasswordDigest dgst = new PasswordDigest(password.toCharArray());
		acc.setSaltDigest(dgst.getSalt(), dgst.getDigest());
		accountStore.createAccount(acc);
		
		return id.getId();
	}

	@Override
	public boolean sendConfirmLink(String emailAddr, String password, String confirm) {
		Preconditions.checkArgument( !Strings.isNullOrEmpty(password) );
		Preconditions.checkArgument( !Strings.isNullOrEmpty(emailAddr) );
		Preconditions.checkArgument( !Strings.isNullOrEmpty(confirm) );
		
		// Send registration email
		return emailSender.sendRegistrationEmail("Пользователь", emailAddr, password, confirm);
	}
}